﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using NUnit.Framework;

namespace PoolKit.Tests
{
    internal static class DisposablePatternTester
    {
        private static readonly List<string> NamesOfMethodsThatMustNotThrow;

        static DisposablePatternTester()
        {
            NamesOfMethodsThatMustNotThrow = new List<string>();

            NamesOfMethodsThatMustNotThrow.AddRange(GetNamesOfPublicInstanceMethods(typeof(IDisposable)));
            NamesOfMethodsThatMustNotThrow.AddRange(GetNamesOfPublicInstanceMethods(typeof(Object)));
        }

        private static IEnumerable<string> GetNamesOfPublicInstanceMethods(Type type)
        {
            return type.GetMethods(BindingFlags.Public | BindingFlags.Instance).Select(x => x.Name);
        }

        public static void MakeSureDisposedObjectIsUnusable(IDisposable disposedObjectToTest)
        {
            if (disposedObjectToTest == null) throw new ArgumentNullException("disposedObjectToTest");

            MethodInfo[] publicMethods =
                disposedObjectToTest.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);

            foreach (MethodInfo publicMethod in publicMethods)
            {
                if (NamesOfMethodsThatMustNotThrow.Contains(publicMethod.Name))
                {
                    continue;
                }

                Assert.Throws<ObjectDisposedException>(
                    delegate()
                        {
                            object[] parameterValues = GetDefaultValuesForParameters(publicMethod);

                            try
                            {
                                publicMethod.Invoke(disposedObjectToTest, parameterValues);
                            }
                            catch (TargetInvocationException exception)
                            {
                                throw exception.InnerException;
                            }
                        },
                    "Expected all public methods of an instance of {0} to throw {1}. Method {2} failed to comply to that contract."
                    .FormatWith(
                        disposedObjectToTest.GetType().FullName,
                        typeof(ObjectDisposedException).FullName,
                        publicMethod.Name
                    ));
            }
        }

        private static object[] GetDefaultValuesForParameters(MethodInfo publicMethod)
        {
            ParameterInfo[] parametersInfo = publicMethod.GetParameters();

            object[] parameterValues = new object[parametersInfo.Length];

            for (int i = 0; i < parametersInfo.Length; i++)
            {
                Type parameterType = parametersInfo[i].ParameterType;

                parameterValues[i] = GetDefaultValueForType(parameterType);
            }
            return parameterValues;
        }

        private static object GetDefaultValueForType(Type parameterType)
        {
            if (parameterType.IsClass)
            {
                return null;
            }

            return Activator.CreateInstance(parameterType);
        }
    }
}